<template>
  <div ref="canvas"></div>
</template>

<script lang="ts" setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import Stats from "three/examples/jsm/libs/stats.module";
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
import { ref, onMounted } from "vue";

// 画布
const canvas = ref<any>('');
// 场景
const scene = new THREE.Scene();
// 相机
let camera = null;
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(120, 60, 180);
camera.lookAt(scene.position);
// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// 平面
const planeGeometry = new THREE.PlaneGeometry(180, 180, 1, 1);
const planeMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.set(0, 0, 0);
scene.add(plane);
// 立方体
const boxGeometry = new THREE.BoxGeometry(4, 4, 4);
for (let i = 0; i < planeGeometry.parameters.height / 5; i++) {
  for (let j = 0; j < planeGeometry.parameters.width / 5; j++) {
    const rnd = Math.random() * 0.75 + 0.25;
    const boxMaterial = new THREE.MeshLambertMaterial({ color : new THREE.Color(rnd, 0, 0) });
    const mesh = new THREE.Mesh(boxGeometry, boxMaterial);
    mesh.position.set(
        -((planeGeometry.parameters.width) / 2) + 2 + (j * 5),
        2,
        -((planeGeometry.parameters.height) / 2) + 2 + (i * 5));
    scene.add(mesh);
  }
}
// 相机观察点
const lookAtGeometry = new THREE.SphereGeometry(2);
const lookAtMaterial = new THREE.MeshLambertMaterial({ color : 0x00ff00 });
const lookAtMesh = new THREE.Mesh(lookAtGeometry, lookAtMaterial);
scene.add(lookAtMesh);

// 光源
const directional = new THREE.DirectionalLight(0xffffff, 0.7);
directional.position.set(-20, 40, 60);
const ambient = new THREE.AmbientLight(0x292929);
scene.add(directional, ambient);
// 相机控件
let orbitControl = new OrbitControls(camera, renderer.domElement);
// 性能统计
const stat = Stats();
stat.setMode(0);
// GUI界面
const controlObj = {
  camera: "透视投影相机",
  switchCamera: () => {
    if (camera instanceof THREE.PerspectiveCamera) {
      // 正交投影摄像机的话，物体被渲染出来的尺寸是不会变化的，因为对象相对于摄像机的距离对渲染的结果是没有影响的
      // 由于正交投影摄像机渲染出的物体大小都是一样的，所以它并不关心使用什么长宽比，或者以什么样的视角来观察场景。
      // 当使用正交投影摄像机时，要定义的是一个需要被渲染的方块区域。
      // 即 上下左右的边界，近面远面的距离
      camera = new THREE.OrthographicCamera(
          window.innerWidth / -16,
          window.innerWidth / 16,
          window.innerHeight / 16,
          window.innerHeight / -16,
          -200,
          500);
      camera.position.set(120, 60, 180);
      camera.lookAt(scene.position);

      orbitControl = new OrbitControls(camera, renderer.domElement);

      controlObj.camera = "正交投影相机";
    } else {
      // 透视投影相机 - 透视视图，也是最自然的视图。距离摄像机越远，物体就会被渲染得越小
      // fov: 视场. 在相机中能看到的那部分场景。计算不能完全显示人眼能够看到的景象，人类拥有解决180度的视场。
      // aspect: 长宽比，决定了横向视场和纵向视场的比例关系
      // near: 定义了从距离相机多近的距离开始渲染。通常这个值会设置尽量小，尽可能的去渲染从相机位置能够看到的所有物体。
      // far: 定义了相机从它所在位置能够看多远，如果设置的比较小，那么场景中有一部分可能不会被渲染，如果设置的比较大，又可能影响渲染的性能
      // zoom: 变焦. 放大或缩小场景. 小于1，场景会被缩小，大于1，场景会被放大.设置为负数，场景会上下颠倒。
      // 摄像机的fov属性决定了横向视场。基于aspect属性，纵向视场也就相应地确定了。
      // near属性决定了近面距离，far属性决定了远面距离。近面距离和远面距离之间的区域将会被渲染。
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.set(120, 60, 180);
      camera.lookAt(scene.position);

      orbitControl = new OrbitControls(camera, renderer.domElement);

      controlObj.camera = "透视投影相机";
    }
  },
};
const gui = new GUI();
// @ts-ignore
gui.add(controlObj, 'switchCamera').name("切换相机");
// @ts-ignore
gui.add(controlObj, 'camera').name("当前相机").listen();
// 观测点移动速度
let step = 0.0;
// 渲染函数
const render = () => {
  stat.update();
  orbitControl.update();

  step += 0.02;
  if (camera instanceof THREE.Camera) {
    const x = 10 + ( 100 * (Math.sin(step)));
    camera.lookAt(new THREE.Vector3(x, 10, 0));
    lookAtMesh.position.copy(new THREE.Vector3(x, 10, 0));
  }

  renderer.render(scene, camera);
  requestAnimationFrame(render);
}

onMounted(() => {
  canvas.value.appendChild(renderer.domElement);
  canvas.value.appendChild(stat.domElement);
  render();
});
</script>
